package com.lauor.smpedr.session.provider;

import com.lauor.smpedr.Configuration;
import com.lauor.smpedr.core.SqlGenerator;
import com.lauor.smpedr.core.executor.Executor;
import com.lauor.smpedr.core.helper.EdrHelper;
import com.lauor.smpedr.exceptions.PersistenceException;
import com.lauor.smpedr.exceptions.QueryException;
import com.lauor.smpedr.param.SqlArgMap;
import com.lauor.smpedr.param.SqlParam;
import com.lauor.smpedr.session.SqlSession;
import com.lauor.smpedr.utils.Str;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;

public class DefaultSqlSession implements SqlSession {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultSqlSession.class);

    private SqlGenerator sqlGenerator;

    private Executor executor;

    private Configuration configuration;
    /** sql session 是否自动提交，非connection的autoCommit */
    private final boolean autoCommit;
    private boolean dirty;

    public DefaultSqlSession(Configuration configuration, SqlGenerator sqlGenerator, Executor executor) {
        this(configuration, sqlGenerator, executor, false);
    }

    public DefaultSqlSession(Configuration configuration, SqlGenerator sqlGenerator, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.sqlGenerator = sqlGenerator;
        this.executor = executor;
        this.autoCommit = autoCommit;
        this.dirty = false;
    }
    //判断是否执行事务提交
    private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

    @Override
    public <E> E selectOne(Class<E> cls, SqlArgMap sqlArgs) {
        return this.selectOne(cls, sqlArgs, new SqlParam(1));
    }

    @Override
    public <E> E selectOne(Class<E> cls, SqlArgMap sqlArgs, SqlParam sqlParam) {
        String tableName = EdrHelper.getTableName(cls);
        return this.selectOne(tableName, cls, sqlArgs, sqlParam);
    }

    @Override
    public <E> E selectOne(String tableName, Class<E> cls, SqlArgMap sqlArgs, SqlParam sqlParam) {
        if (sqlParam == null){
            sqlParam = new SqlParam(0, 1);
        } else {
            if ( !sqlParam.hasLimit() ){
                sqlParam.setStart(0);
                sqlParam.setPageSize(1);
            }
        }
        String sql = sqlGenerator.generateQuery(tableName, sqlArgs, sqlParam);
        return this.selectOne(sql, cls, sqlArgs);
    }

    @Override
    public <E> E selectOne(String sql, Class<E> cls, SqlArgMap sqlArgs) {
        try {
            List<E> results = executor.<E>query(sql, sqlArgs, cls, configuration.getMethodSignature("selectOne"));
            if (results.size() > 0){
                return results.get(0);
            } else {
                return null;
            }
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new QueryException("Error querying database", e);
        }
    }

    @Override
    public <E> List<E> selectList(Class<E> cls, SqlArgMap sqlArgs) {
        return this.selectList(cls, sqlArgs, null);
    }

    @Override
    public <E> List<E> selectList(Class<E> cls, SqlArgMap sqlArgs, SqlParam sqlParam) {
        String tableName = EdrHelper.getTableName(cls);
        return this.selectList(tableName, cls, sqlArgs, sqlParam);
    }

    @Override
    public <E> List<E> selectList(String tableName, Class<E> cls, SqlArgMap sqlArgs, SqlParam sqlParam) {
        String sql = sqlGenerator.generateQuery(tableName, sqlArgs, sqlParam);
        return this.selectList(sql, cls, sqlArgs);
    }

    @Override
    public <E> List<E> selectList(String sql, Class<E> cls, SqlArgMap sqlArgs) {
        try {
            return executor.query(sql, sqlArgs, cls, configuration.getMethodSignature("selectList"));
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new QueryException("Error querying database", e);
        }
    }

    @Override
    public <E> int executeUpdate(String sql, List<E> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return 0;
        }
        try {
            return executor.executeBatch(sql, dataList, dataList.get(0).getClass());
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new PersistenceException("Error updating database", e);
        }
    }

    @Override
    public <E> int insert(Class<E> cls, E data) {
        return this.insert(cls, data, true);
    }

    @Override
    public <E> int insert(Class<E> cls, E data, boolean insertNull) {
        String tableName = EdrHelper.getTableName(cls);
        return this.batchInsert(cls, tableName, Arrays.asList(data), insertNull);
    }

    @Override
    public <E> int insert(String tableName, E data) {
        return this.insert(tableName, data, true);
    }

    @Override
    public <E> int insert(String tableName, E data, boolean insertNull) {
        if (data == null) return 0;
        return this.batchInsert(tableName, Arrays.asList(data), insertNull);
    }

    @Override
    public <E> int batchInsert(Class cls, List<E> dataList) {
        return this.batchInsert(cls, dataList, true);
    }

    @Override
    public <E> int batchInsert(Class cls, List<E> dataList, boolean insertNull) {
        String tableName = EdrHelper.getTableName(cls);
        return this.batchInsert(cls, tableName, dataList, insertNull);
    }

    @Override
    public <E> int batchInsert(String tableName, List<E> dataList) {
        return this.batchInsert(tableName, dataList, true);
    }

    @Override
    public <E> int batchInsert(String tableName, List<E> dataList, boolean insertNull) {
        if (dataList == null || dataList.isEmpty()) return 0;

        return batchInsert(dataList.get(0).getClass(), tableName, dataList, insertNull);
    }

    private  <E> int batchInsert(Class cls, String tableName, List<E> dataList, boolean insertNull) {
        if (dataList == null || dataList.isEmpty()) return 0;

        String sql = sqlGenerator.generateBatchInsert(cls, tableName, dataList, insertNull);
        if (Str.isNull(sql) ) return 0;
        try {
            return executor.executeBatch(sql, dataList, cls);
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new PersistenceException("Error updating database", e);
        }
    }

    @Override
    public int update(Object obj, SqlArgMap sqlArgs) {
        return this.update(obj, sqlArgs, true);
    }

    @Override
    public int update(Object obj, SqlArgMap sqlArgs, boolean updateNull) {
        return update(obj, sqlArgs, null, updateNull);
    }

    @Override
    public int update(Object obj, SqlArgMap sqlArgs, SqlParam sqlParam, boolean updateNull) {
        if (obj == null) return 0;
        String tableName = EdrHelper.getTableName( obj.getClass() );
        return update(tableName, obj, sqlArgs, sqlParam, updateNull);
    }

    @Override
    public int update(String tableName, Object obj, SqlArgMap sqlArgs, boolean updateNull) {
        return update(tableName, obj, sqlArgs, null, updateNull);
    }

    @Override
    public int update(String tableName, Object obj, SqlArgMap sqlArgs, SqlParam sqlParam, boolean updateNull) {
        if (obj == null) return 0;
        String sql = sqlGenerator.generateUpdate(tableName, obj, sqlArgs, sqlParam, updateNull);
        this.dirty = true;
        try {
            return executor.update(sql, obj, sqlArgs);
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new PersistenceException("Error updating database", e);
        }
    }

    @Override
    public int delete(Class cls, SqlArgMap sqlArgs, SqlParam sqlParam) {
        String tableName = EdrHelper.getTableName(cls);
        return this.delete(tableName, sqlArgs, sqlParam);
    }

    @Override
    public int delete(Class cls, SqlArgMap sqlArgs) {
        String tableName = EdrHelper.getTableName(cls);
        return this.delete(tableName, sqlArgs, null);
    }

    @Override
    public int delete(String tableName, SqlArgMap sqlArgs, SqlParam sqlParam) {
        String sql = sqlGenerator.generateDelete(tableName, sqlArgs, sqlParam);
        try {
            return executor.delete(sql, sqlArgs);
        } catch (Exception e) {
            LOG.error(sql, e);
            throw new PersistenceException("Error updating database", e);
        }
    }

    @Override
    public int delete(String tableName, SqlArgMap sqlArgs) {
        return delete(tableName, sqlArgs, null);
    }

    @Override
    public void commit(boolean force) {
        try {
            executor.commit( this.isCommitOrRollbackRequired(force) );
            this.dirty = false;
        } catch (Exception e) {
            LOG.error("", e);
            throw new RuntimeException("Error committing transaction", e);
        }
    }

    @Override
    public void commit() {
        this.commit(false);
    }

    @Override
    public void rollback(boolean force) {
        try {
            executor.rollback( this.isCommitOrRollbackRequired(force) );
            this.dirty = false;
        } catch (Exception e) {
            LOG.error(String.format("rollback(%s)", force), e);
            throw new RuntimeException("Error rolling back transaction", e);
        }
    }

    @Override
    public void rollback() {
        this.rollback(false);
    }

    @Override
    public void close() {
        try {
            executor.close( this.isCommitOrRollbackRequired(false) );
            this.dirty = false;
        } catch (Exception e) {
            LOG.error("", e);
            throw new RuntimeException("Error closing connection", e);
        }
    }

    @Override
    public boolean isClosed() {
        return this.executor.isClosed();
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }
}